package cn.enited.devtools.apiaggregator.api.analysis;

import cn.enited.devtools.apiaggregator.api.utils.SrvVariableUtil;
import cn.enited.devtools.apiaggregator.api.vo.ApiAnalysisObject;
import cn.enited.devtools.apiaggregator.api.vo.ApiParameter;
import cn.enited.devtools.apiaggregator.api.vo.RemoteService;
import cn.enited.devtools.apiaggregator.consts.ApiConst;
import cn.enited.devtools.apiaggregator.consts.ErrorCode;
import cn.enited.devtools.apiaggregator.converter.ApiAnalysisObjectConverter;
import cn.enited.devtools.apiaggregator.entity.ApiAggObject;
import cn.enited.devtools.apiaggregator.exception.BizException;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.stream.Collectors;

@Component
public class ApiAnalyzer {

    private ApiAnalysisObjectConverter objectConverter;

    @Autowired
    public void setObjectConverter(ApiAnalysisObjectConverter objectConverter) {
        this.objectConverter = objectConverter;
    }

    /**
     * 分析Api聚合配置并转换成分析对象
     *
     * @param apiAggObject
     * @return
     */
    public ApiAnalysisObject analyse(ApiAggObject apiAggObject) throws BizException {
        ApiAnalysisObject analysisObject = objectConverter.apiAggObject2AnalysisObject(apiAggObject);

        apiAggObject.getServices().forEach(entry -> {
            RemoteService srv = objectConverter.apiAggEntry2RemoteService(entry);

            // 分析path variable
            List<String> pathVariables = SrvVariableUtil.extractParamNames(srv.getPath());
            pathVariables.forEach(paramName -> {
                Optional<ApiParameter> param = analysisComplexParamName(paramName, srv.getDependServiceIds());
                param.ifPresent(p -> srv.getPathVariableParameters().putIfAbsent(paramName, p));
            });

            // 分析 head
            entry.getHead().forEach((headName, paramName) -> {
                Optional<ApiParameter> param = analysisComplexParamName(paramName, srv.getDependServiceIds());
                param.ifPresent(p -> srv.getHeaderParameters().put(headName, paramName, p));
            });

            // 分析 request body
            if (MapUtil.isNotEmpty(entry.getData())) {
                JSONObject requestBody = new JSONObject(entry.getData());
                srv.setPrimitiveRequestBody(requestBody);
                analysisRequestBody(requestBody, srv);
            }

            // 没有依赖其他接口时说明该服务可以进行并行调用，效率是最高的
            if (CollectionUtil.isEmpty(srv.getDependServiceIds())) {
                analysisObject.addFreeService(srv);
            } else {
                // 添加到接口依赖列表时需要计算插入的位置，srv要在被依赖的接口后面以保证顺序调用接口取response，
                // 其实analysisObject.dependService这个列表就是一个stack
                int maxOrder = ApiConst.UNFOUND_INDEX;
                for (String id : srv.getDependServiceIds()) {
                    validateDependServiceId(id, srv, analysisObject);
                    int index = indexOfDependServiceId(id, analysisObject.getDependService());
                    if (maxOrder < index) {
                        maxOrder = index;
                    }
                }
                analysisObject.addDependService(maxOrder + 1, srv);
                // 还有一种情况：A和B之间没有依赖关系，分别依赖于其他接口，当各自依赖的接口调用完之后，A和B就可以并行调用
                // 还没想好如何判断，后续再加吧
            }
        });

        // 检查循环依赖
        checkCircularDependency(analysisObject);

        return analysisObject;
    }

    private void validateDependServiceId(String dependSrvId, RemoteService srv, ApiAnalysisObject analysisObject) throws BizException {
        if (!analysisObject.getServiceIds().contains(dependSrvId)) {
            throw new BizException(ErrorCode.NOT_EXIST.code(),
                    StrUtil.format("服务[{}]依赖了不识别的服务id:{}", srv.getId(), dependSrvId));
        }
    }

    /**
     * 检查是否存在循环依赖，正常的依赖关系应该是DAG
     * 参考：https://blog.csdn.net/yafeichang/article/details/53893120
     *
     * @throws BizException
     */
    private void checkCircularDependency(ApiAnalysisObject analysisObject) throws BizException {
        if (CollectionUtil.isNotEmpty(analysisObject.getDependService())) {
            // 用图搜索的算法检查是否存在循环依赖
            List<Digraph> digraphs = analysisObject.getDependService()
                    .parallelStream().map(Digraph::new).collect(Collectors.toList());
            try {
                digraphs.forEach(this::detectDependencyRing);
            } finally {
                digraphs.forEach(Digraph::release);
                digraphs.clear();
            }
        }
    }

    /**
     * 检测环的存在
     *
     * @param digraph 图
     */
    private void detectDependencyRing(Digraph digraph) throws BizException {
        RingDetectionState state = new RingDetectionState(digraph.vertexSize());
        try {
            for (String vertex : digraph.getVertices()) {
                if (!state.marked(vertex)) {
                    dfs(digraph, vertex, state);
                }
            }
            if (state.hasRing()) {
                List<String> vertices = state.getRing();
                String error = StrUtil.format("检测到环的存在：{}", CollectionUtil.join(vertices, " -> "));
                throw new BizException(ErrorCode.INVALID_DATA.code(), error);
            }
        } finally {
            state.clear();
        }
    }

    /**
     * 用于递归检测环的状态对象
     */
    private static class RingDetectionState {
        // 记录访问过的顶点
        private Map<String, Boolean> marked;
        // 记录前向访问的顶点，用于返回组成环的路径
        private Map<String, String> edgeTo;
        // 一个顶点是否在一个递归顺序里的标记，进入递归时标记为true，退出递归时设置为false
        private Map<String, Boolean> onStack;

        private boolean ringExist;

        private Stack<String> ringVertices;

        public RingDetectionState(int vertices) {
            this.marked = new HashMap<>(vertices);
            this.edgeTo = new HashMap<>(vertices);
            this.onStack = new HashMap<>(vertices);
        }

        public boolean marked(String v) {
            return marked.getOrDefault(v, false);
        }

        public void setMarked(String v, boolean flag) {
            marked.put(v, flag);
        }

        public String getEdgeTo(String v) {
            return edgeTo.get(v);
        }

        public void setEdgeTo(String v, String w) {
            edgeTo.put(v, w);
        }

        public boolean isOnStack(String v) {
            return onStack.getOrDefault(v, false);
        }

        public void putOnStack(String v, boolean flag) {
            onStack.put(v, flag);
        }

        public boolean hasRing() {
            return ringExist;
        }

        public void setRingExist() {
            ringExist = true;
        }

        public List<String> getRing() {
            return ringVertices;
        }

        public void addRingVertex(String v) {
            if (Objects.isNull(ringVertices)) {
                ringVertices = new Stack<>();
            }
            ringVertices.push(v);
        }

        public void clear() {
            this.marked.clear();
            this.marked = null;
            this.edgeTo.clear();
            this.edgeTo = null;
            this.onStack.clear();
            this.onStack = null;
            if (Objects.nonNull(this.ringVertices)) {
                this.ringVertices.clear();
                this.ringVertices = null;
            }
        }
    }

    /**
     * 深度优先搜索
     *
     * @param digraph 图
     * @param v    搜索顶点
     * @param state   搜索状态对象
     */
    private void dfs(Digraph digraph, String v, RingDetectionState state) {
        // 标记搜索顶点已进入递归
        state.putOnStack(v, true);
        // 标记搜索顶点已被访问过
        state.setMarked(v, true);
        // 环检测要点就是，找到一个还在遍历中的节点，同时在遍历的时候它如果再次被访问到了，则表示找到了环。
        // 而如果它被访问完了之后，再次碰到它的时候就不是环了。
        /*
         * 下面这个顶点6不是环，因为是从顶点1的两条边出发遍历的
         * 1->2->4->6
         * 1->3->5->7->6
         * 下面这个顶点4形成了环，因为一次遍历尚未退出的时候访问到了2次顶点4
         * 1->3->5->7->4->6->8->4
         */
        for (String w : digraph.getAdjacencyList(v)) {
            if (state.hasRing()) {
                return;
            }
            // 沿着v的边，如果没有标记过则递归遍历
            if (!state.marked(w)) {
                // 反向记录，方便找到环的时候倒退着查
                state.setEdgeTo(w, v);
                dfs(digraph, w, state);
            } else if (state.isOnStack(w)) { // 遍历尚未结束时又遇到了该节点，说明产生了环
                state.setRingExist();
                // 检测到环的存在后依次把环中的顶点取出来压入栈中，最后提示给用户
                for (String x = v; !x.equalsIgnoreCase(w); x = state.getEdgeTo(x)) {
                    state.addRingVertex(x);
                }
                state.addRingVertex(w);
                state.addRingVertex(v);
            }
        }
        // 退出递归，还原状态
        state.putOnStack(v, false);
    }

    /**
     * 获取被依赖的接口在列表中的索引
     *
     * @param id
     * @param services
     * @return
     */
    private int indexOfDependServiceId(String id, List<RemoteService> services) {
        if (CollectionUtil.isEmpty(services)) {
            return ApiConst.UNFOUND_INDEX;
        }
        // 重写了RemoteService的equals和hashCode
        return services.indexOf(new RemoteService(id));
    }

    /**
     * 递归循环request body获取变量
     *
     * @param jsonObject
     * @param srv
     */
    private void analysisRequestBody(JSONObject jsonObject, RemoteService srv) {
        // 递归的目的就是取每一层中的字符串进行分析，碰到JSONObject和JSONArray就递归，
        // 如果是integer、long、double、boolean等就当成是常量，跳过即可
        jsonObject.forEach((key, value) -> {
            if (value instanceof String) {
                String paramName = (String) value;
                Optional<ApiParameter> param = analysisComplexParamName(paramName, srv.getDependServiceIds());
                param.ifPresent(p -> srv.getRequestBodyParameters().putIfAbsent(paramName, p));
            } else if (value instanceof JSONObject) {
                analysisRequestBody((JSONObject) value, srv);
            } else if (value instanceof JSONArray) {
                ((JSONArray) value).forEach(item -> {
                    if (item instanceof String) {
                        String paramName = (String) item;
                        Optional<ApiParameter> param = analysisComplexParamName(paramName, srv.getDependServiceIds());
                        param.ifPresent(p -> srv.getRequestBodyParameters().putIfAbsent(paramName, p));
                    } else if (item instanceof JSONObject) {
                        analysisRequestBody((JSONObject) item, srv);
                    }
                });
            }
        });
    }

    /**
     * 分析复杂的参数变量名
     *
     * @param possibleParamName 要分析的参数字符串
     * @param dependApiIds      参数如果是依赖的服务接口名称则存储在此容器中
     * @return
     */
    private Optional<ApiParameter> analysisComplexParamName(String possibleParamName, Collection<String> dependApiIds) {
        // 如果参数字符串是个常量则不做分析直接返回
        if (!SrvVariableUtil.isArrayVar(possibleParamName)) {
            return Optional.empty();
        }

        ApiParameter topParam = new ApiParameter();
        String varName = null;

        if (SrvVariableUtil.isHeaderVar(possibleParamName)) { // 参数是从head中取值，格式如#token
            varName = StrUtil.removePrefix(possibleParamName, SrvVariableUtil.VAR_TOKEN_HEADER);
            topParam.setSource(ApiConst.PARAM_SOURCE.HEAD)
                    .setType(ApiConst.PARAM_TYPE.PRIMITIVE);
        } else if (SrvVariableUtil.isServiceNameVar(possibleParamName)) { // 参数需要依赖其他接口，格式如@service
            varName = StrUtil.removePrefix(possibleParamName, SrvVariableUtil.VAR_TOKEN_SERVICE);
            topParam.setSource(ApiConst.PARAM_SOURCE.RESPONSE);
            dependApiIds.add(varName);
        } else { // 参数是从request body中取值，变量名无任何前缀
            varName = possibleParamName;
            topParam.setSource(ApiConst.PARAM_SOURCE.REQUEST_BODY);
        }
        topParam.setName(varName);

        // 带.的表示是对象类型参数，类似serviceName.data这样的结构
        if (StrUtil.contains(varName, StrUtil.DOT)) {
            List<String> fieldChain = StrUtil.split(varName, StrUtil.DOT, true, true);
            String serviceName = fieldChain.get(0);
            analysisSimpleParamName(serviceName, topParam);
            // 一层一层的分解得到子参数对象并赋值到父参数对象的field字段中
            if (fieldChain.size() > 1) {
                ApiParameter currParam = topParam;
                for (int i = 1; i < fieldChain.size(); i++) {
                    String field = fieldChain.get(i);
                    ApiParameter subParam = new ApiParameter();
                    subParam.setName(field)
                            .setSource(currParam.getSource()); // 子参数对象的source无意义，与父对象保持一致即可
                    analysisSimpleParamName(field, subParam);
                    currParam.setField(subParam);
                    currParam = subParam;
                }
            }
        } else {
            analysisSimpleParamName(varName, topParam);
        }
        return Optional.of(topParam);
    }

    /**
     * 解析变量名，如果有数组则进行分解
     *
     * @param varName 变量名
     * @param param   参数对象
     */
    private void analysisSimpleParamName(String varName, ApiParameter param) {
        // 有.说明是JSON对象或者JSON数组
        if (SrvVariableUtil.isArrayVar(varName)) { // 类似@serviceName$0、@serviceName$_这样的结构
            String[] s = StrUtil.splitToArray(varName, SrvVariableUtil.VAR_TOKEN_ARRAY);
            param.setName(s[0]).setType(ApiConst.PARAM_TYPE.JSON_ARRAY);
            // $_代表要提取整个数组，param.arrayIndex不赋值
            if (NumberUtil.isNumber(s[1])) {
                param.setArrayIndex(Integer.valueOf(s[1]));
            }
        } else {
            param.setName(varName).setType(ApiConst.PARAM_TYPE.RT_OBJECT);
        }
    }
}
